package org.sciom

import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.Map

object ScIomObject {
  val rawClone = (proto: ScIomObject) => {
    var self = proto.allocate
    self.tag = proto.tag
    self.setProtoTo(proto)
    self.isDirty = true
    self
  }

  val selfFunc = (self: ScIomObject, locals: ScIomObject, m: ScIomMessage) => {
    self
  }
}

class ScIomObject(val state: ScIomState) {
  var tag : ScIomTag = new ScIomTag(state, "Object", ScIomObject.rawClone)

//  var listeners

  var activatable = false
  var ownsSlots = true
  var hasDoneLookup = false
  var isLocals = false
  var isDirty = false

  var slots = scala.collection.mutable.Map.empty[ScIomSymbol, ScIomObject]
  var protos = new ArrayBuffer[ScIomObject]()

  def SYMBOL(value: String) = state.symbolWithString(value)

  def allocate = new ScIomObject(state)

  def doString(value: String) : ScIomObject = {
    rawDoString(state.symbolWithString(value))
  }
  
  def setProtoTo(proto: ScIomObject) : Unit = {
    rawSetProto(proto)

    if(slots.isEmpty) {
      slots = proto.slots
      ownsSlots = false
    }
  }

  def rawRemoveAllProtos() = protos.clear

  def rawSetProto(proto: ScIomObject) : Unit = {
    rawRemoveAllProtos
    protos += proto
  }

  def setSlotTo(slotName: ScIomSymbol, value: ScIomObject) : Unit = {
    inlineSetSlotTo(slotName, value)
  }

  def inlineSetSlotTo(slotName: ScIomSymbol, value: ScIomObject) : Unit = {
    slots(slotName) = value
  }

  // Io: IoObject_name
  def name() : String = {
	// If self has a type slot which is a string, then use that instead of the tag name
    var tp = rawGetSlot(SYMBOL("type"))
    if(tp != null && tp.isInstanceOf[ScIomSequence]) {
      return tp.asInstanceOf[ScIomSymbol].string
    }

    tag.name
  }

  // Io: IoObject_rawGetSlot_
  def rawGetSlot(slotName: ScIomSymbol) : ScIomObject = {
    if(ownsSlots) {
      if(slots.contains(slotName)) {
        return slots(slotName)
      }
    }
    
    hasDoneLookup = true

    var i = 0
    var v : ScIomObject = null
    while(i < protos.length) {
      val p = protos(i)
      if(!p.hasDoneLookup) {
        v = p.rawGetSlot(slotName)

        if(v != null) {
          i = protos.length
        }
      }
         
      i += 1
    }
    
    hasDoneLookup = false

    v
  }

  // Io: IoObject_rawDoString_label_
  def rawDoString(string: ScIomSymbol) : ScIomObject = {
println("rawDoString: " + string)

    val cm = ScIomMessage.newWithName(state, SYMBOL("Compiler"))
    val messageForString = ScIomMessage.newWithName(state, SYMBOL("messageForString"))

    cm.next = messageForString

    messageForString.addCachedArg(string)

    val newMessage  = cm.withLocalsAndPerformOn(this, this)

    if(newMessage != null) {
      return newMessage.withLocalsAndPerformOn(this, this)
    }
    
    return state.ioNil
  }

  def withLocalsAndPerformOn(locals: ScIomObject, target: ScIomObject) : ScIomObject = {
    throw new UnsupportedOperationException("can't perform on object that is not message: " + this)
  }

  def rawGetSlotWithContext(slotName: ScIomSymbol) : (ScIomObject, ScIomObject) = {
    var v : (ScIomObject, ScIomObject) = (null, null)

    if(ownsSlots) {
      if(slots.contains(slotName)) {
        return (this, slots(slotName))
      }
    }

    hasDoneLookup = true
    v = findMatchInProtos(protos, v, slotName)
    hasDoneLookup = false

    v
  }

  def findMatchInProtos(prots: ArrayBuffer[ScIomObject], v2: (ScIomObject, ScIomObject), slotName: ScIomSymbol) : (ScIomObject, ScIomObject) = {
    var i = 0
    var v = v2

    while(i < prots.length) {
      val curr = prots(i)

      if(!curr.hasDoneLookup) {
        v = curr.rawGetSlotWithContext(slotName)

        if(v._2 != null) {
          i = prots.length
        }
      }
      i += 1
    }
    v
  }

  def activate(target: ScIomObject, locals: ScIomObject, m: ScIomMessage, slotContext: ScIomObject) : ScIomObject = {
    if(activatable) {
      return tag.activateFunc(this, target, locals, m, slotContext)
    } else {
      return this
    }
  }

  def forward(locals: ScIomObject, m: ScIomMessage) : ScIomObject = {
    val (context, forwardSlot) = rawGetSlotWithContext(state.forwardSymbol)

    if(null != forwardSlot) {
      return forwardSlot.activate(this, locals, m, context)
    }

    state.error(m, "%s does not respond to message '%s'", name, m.mname.string)

    return this
  }

  def perform(locals: ScIomObject, m: ScIomMessage) : ScIomObject = {
    println("perform1: " + m)
    val (context, slotValue) = rawGetSlotWithContext(m.mname)
    
    if(null != slotValue) {
      return slotValue.activate(this, locals, m, context)
    }

    if(isLocals) {
      return localsForward(locals, m)
    }

    forward(locals, m)
  }

  def localsForward(locals: ScIomObject, m: ScIomMessage) : ScIomObject = {
    if(slots.contains(state.selfSymbol)) {
      val selfDelegate = slots(state.selfSymbol)
      if(selfDelegate != this) {
        return selfDelegate.perform(locals, m)
      }
    }

    state.ioNil    
  }

  def IOCLONE = {
    tag.cloneFunc(this)
  }

  def addMethodTable(table: List[(String, (ScIomObject, ScIomObject, ScIomMessage) => ScIomObject)]) : Unit = table match {
    case first :: rest => {
      addMethod(state.symbolWithString(first._1), first._2)
      addMethodTable(rest)
    }
    case Nil => null
  }

  def addMethod(name: ScIomSymbol, func: (ScIomObject, ScIomObject, ScIomMessage) => ScIomObject) : ScIomObject = {
    var f = new ScIomClosureInvoker(state, func, tag, name.string)
    f.activatable = true
    setSlotTo(name, f)
    f
  }

  override def toString = {
    val tp = rawGetSlot(SYMBOL("type"))
    if(tp == null) {
      tag.name + "{}"
    } else {
      "" + tp + "!" + tag.name + "{}"
    }
  }
}
